iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Vibe Coding

透過 vibe coding 製作一套基於社群打造的互動式遊戲系列 第 22 篇

[#022]同一個按鈕按了兩次,為什麼不能扣兩次錢?

  • 分享至 

  • xImage
  •  

上一篇文章,我談到 AI 的表達和實際操作需要分開。角色可以自由選擇說話方式,但金額、權限與操作結果,要由明確的程式流程處理。

接下來,我想把其中一個問題拉近一點。

假設一位成員在微光裡按下購買按鈕,畫面卻沒有馬上回應。他不確定剛才有沒有按到,於是又按了一次。

對他來說,可能只是想買一件東西。

但如果程式把每次收到的點擊都當成新的購買,就可能扣了兩次金幣,也交付了兩份商品。

兩邊看到的事情並不一樣。成員以為自己在重試,系統卻以為他又買了一次。

這篇就從這個很小的情境,談我希望怎麼處理重複操作。以下是設計原則,不代表微光目前所有交易功能都已經完成相同機制。

重複點擊,不一定是使用者操作錯誤
使用網頁或 Bot 時,按下按鈕後沒有回應,再按一次,是很自然的反應。

可能是網路慢,也可能是程式還在處理。使用者不知道背後發生什麼,只能透過眼前的畫面判斷。

而且,重複請求不一定來自人。

有些傳輸或重試機制,也可能讓同一件事情再次送到程式面前。因此,不能只在說明裡寫上「請勿重複點擊」,就認為問題已經處理。

那可以是一句提醒,但不應該是避免重複扣款的主要保障。

我希望系統能預期這些情況,讓一次普通的重試,不至於變成需要人工補救的帳務問題。

先讓這次購買有自己的編號
要辨認是不是重複操作,需要先知道「同一次」指的是什麼。

一種做法,是在購買確認階段,替這次操作建立一個固定編號。後續送出、查詢與重試,都沿用這個編號。

程式收到請求時,就能先檢查:這次購買是不是已經處理過?

如果已經完成,就回傳原本的結果,不再扣一次金幣。

這類做法常被稱為「冪等」。詞聽起來有點抽象,放在這個例子裡,就是同一筆操作重複送來,也不會因此重複產生交易效果。

但前提很重要:重試必須仍然被辨認成同一筆操作。

如果每次點擊都重新建立一個購買編號,系統看到的仍然會是不同交易。光是有編號,還不能解決問題。

重試一次,和再買一件,需要有不同入口
防止重複,不代表同一個人不能再次購買同一項商品。

成員可能真的想再買一件。這時候,系統應該建立新的交易,而不是一直回覆上一筆已完成。

所以,我需要在操作流程裡分清楚兩種意圖:

一種是確認剛才那次到底有沒有成功,另一種是開始一筆新的購買。

例如,原本的確認按鈕只對應那次交易。完成後,畫面顯示結果;若想再買,則透過明確的「再次購買」重新開始。

同時,那個操作編號也應該綁定原本的使用者、商品和確認內容。不能沿用同一個編號,卻偷偷換成另一件商品或不同金額。

編號是在辨認一件確定的事情,不是一張可以任意改寫的通行證。

把按鈕變灰,只能處理畫面上的一部分
最容易想到的改善,是按下之後立刻停用按鈕,並顯示「處理中」。

這很有幫助。

成員知道系統有收到,也比較不會因為沒有反應而連續點擊。但這仍然不是完整的防護。

因為停用畫面之前,請求可能已經送出了不只一次。使用者也可能開著不同畫面,或遇到其他重複傳送的情況。

因此,我想把兩件事一起做。

畫面上的提示,減少不必要的重試;真正處理交易的程式,則要確保重複請求不會再次生效。

不能因為正常使用時看起來按不了第二次,就假設程式永遠只會收到一次。

兩個請求同時到達,也不能都通過
即使程式會先查詢「是否處理過」,還有另一個問題。

假設兩個請求幾乎同時到達。第一個查到尚未處理,第二個也在結果寫入前查到尚未處理,接著兩邊都開始扣款。

每一邊單獨看,好像都有檢查。但放在一起,檢查仍然失效了。

所以,不能只靠「先查一次,再做事情」這種分開的步驟。

需要讓資料庫或其他可靠的協調機制,確保同一個操作編號只能被正式接受一次。例如,利用唯一性限制和交易處理,讓互相競爭的請求不能各自成立。

這部分不需要成員理解,但系統必須處理。因為真正的使用情境,不會保證大家按照程式最容易應付的順序操作。

扣款與交付,也不能各自成功一半
避免重複扣款之外,還要考慮流程做到一半時出了問題。

如果金幣扣了,商品卻沒有加入背包,這筆購買就沒有完整完成。反過來,商品已經交付,扣款卻失敗,也會讓紀錄不一致。

若帳戶、商品與交易紀錄都由同一個資料庫管理,可以把相關變更放在同一個資料庫交易裡:全部成功才確認,否則一起退回。

但如果交付需要呼叫外部服務,就不能假設本地資料庫能替所有事情一起收尾。

這時候,需要留下明確的處理狀態,知道哪一步已經完成、哪一步仍待確認。恢復處理時,也要從有證據的位置繼續,而不是把整筆流程重新跑一次。

否則,原本想修復交付失敗,反而可能再扣一次錢。

沒收到成功訊息,不等於交易失敗

還有一種情況很容易造成誤會:交易已經完成,但通知沒有送出去。

成員看不到結果,以為失敗;程式裡卻已經有扣款與交付紀錄。

因此,我希望成功訊息是用來告知結果,而不是唯一能證明結果的地方。

交易本身需要有可查詢的紀錄。遇到連線中斷或回覆失敗時,可以依照操作編號確認,而不是憑畫面上有沒有出現一句話判斷。

如果目前還查不清楚,就應該說結果待確認,不急著把它標成失敗,也不催使用者重新購買。

讓人多等一下,可能不夠俐落,但比在未知狀態下再扣一次更合適。

防重紀錄,要留得比這次執行更久
如果程式只在記憶體裡記住「這筆做過了」,重新啟動之後,紀錄可能就消失。

晚一點到達的重試,便可能被當成第一次收到。

因此,涉及扣款與交付的處理結果,需要保存在能跨越程式重啟的地方。保留多久,也要考慮實際可能出現的重試、查詢和對帳需求,不能隨意清掉就當作沒有後續影響。

紀錄的內容不必什麼都存,但至少要能回答:是哪一筆操作、處理到哪裡,以及最後結果是什麼。

這些資訊也能在成員提出疑問時,幫助我查清楚,而不是只能請他回想自己按了幾次。

不讓使用者替系統的小意外付兩次代價
我希望微光的購買流程,對成員來說可以很簡單:確認內容,按下按鈕,知道結果。

為了維持這份簡單,背後反而需要處理重試、同時操作、中途失敗,以及通知遺失等情況。

這些不是罕見到可以完全忽略的邊角,也不適合全靠一句「請小心操作」交給使用者承擔。

下一次有人因為畫面沒有回應,又按了一次按鈕,我希望系統能認出那仍然是同一筆購買,回覆他原本的結果。

他只是想確認剛才那件事有沒有完成。

不應該因此,多付一次錢。


上一篇
[#021]AI 可以自由發揮,但哪些事情不能讓它自己決定?
下一篇
[#023]功能壞掉的時候,先讓使用的人知道發生了什麼
系列文
透過 vibe coding 製作一套基於社群打造的互動式遊戲 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言